/*-------------------------------------------------------------------------
 *
 * dirmod.c
 *	  目录处理函数
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *	这包括在 Win32 (NT4 及更新版本) 上工作的函数的替代版本。
 *
 * IDENTIFICATION
 *	  src/port/dirmod.c
 *
 *-------------------------------------------------------------------------
 */

#ifndef FRONTEND
#include "postgres.h"
#else
#include "postgres_fe.h"
#endif

/* 不要修改系统头文件中的声明 */
#if defined(WIN32) || defined(__CYGWIN__)
#undef rename
#undef unlink
#endif

#include <unistd.h>
#include <sys/stat.h>

#if defined(WIN32) || defined(__CYGWIN__)
#ifndef __CYGWIN__
#include <winioctl.h>
#else
#include <windows.h>
#include <w32api/winioctl.h>
#endif
#endif

#if defined(WIN32) || defined(__CYGWIN__)

/*
 *	pgrename
 */
int pgrename(const char *from, const char *to)
{
	int			loops = 0;

	/*
	 * 我们需要循环，因为即使 PostgreSQL 使用了允许在文件打开时重命名的标志，
	 * 其他应用程序可能在没有这些标志的情况下打开文件。不过，我们不会无限期等待
	 * 其他人关闭文件，因为调用者可能会持有锁并阻塞其他后端。
	 */
#if defined(WIN32) && !defined(__CYGWIN__)
	while (!MoveFileEx(from, to, MOVEFILE_REPLACE_EXISTING))
#else
	while (rename(from, to) < 0)
#endif
	{
#if defined(WIN32) && !defined(__CYGWIN__)
		DWORD		err = GetLastError();

		_dosmaperr(err);

		/*
		 * 现代的基于 NT 的 Windows 版本如果另一个进程在没有 FILE_SHARE_DELETE 的情况下
		 * 打开文件，会返回 ERROR_SHARING_VIOLATION。
		 * 在某些防病毒软件的情况下也曾看到 ERROR_LOCK_VIOLATION。这过去只检查
		 * ERROR_ACCESS_DENIED，所以可能在某些操作系统版本中你也会得到该错误。我们不
		 * 预期在当前使用 rename() 的地方出现实际的权限错误。
		 */
		if (err != ERROR_ACCESS_DENIED &&
			err != ERROR_SHARING_VIOLATION &&
			err != ERROR_LOCK_VIOLATION)
			return -1;
#else
		if (errno != EACCES)
			return -1;
#endif

		if (++loops > 100)		/* 超时 10 秒 */
			return -1;
		pg_usleep(100000);		/* us */
	}
	return 0;
}


/*
 *	pgunlink
 */
int pgunlink(const char *path)
{
	int			loops = 0;
	struct stat st;

	/*
	 * 这个函数可能被用于常规文件或交接点（我们用其模拟符号链接）。后者
	 * 必须在 Windows 上通过 rmdir() 解除链接。在我们担心那些之前，让我们
	 * 看看是否可以直接解除链接，因为这被预期是最常见的情况。
	 */
	if (unlink(path) == 0)
		return 0;
	if (errno != EACCES)
		return -1;

	/*
	 * EACCES 由于多种原因被报告，包括对交接点的 unlink()。检查是否是这种情况，
	 * 以便我们可以重定向到 rmdir()。
	 *
	 * 请注意，由于我们只检查一次，我们无法处理在我们因共享冲突而重试时
	 * 从常规文件变为交接点的路径，但这似乎不太可能。我们或许可以通过在 lstat()
	 * 和重试循环之间持有一个文件句柄来阻止这种情况，但目前看来这似乎是过度设计。
	 */
	if (lstat(path, &st) < 0)
		return -1;

	/*
	 * 我们需要循环，因为即使 PostgreSQL 使用了允许在文件打开时解除链接的标志，
	 * 其他应用程序可能在没有这些标志的情况下打开文件。不过，我们不会无限期等待
	 * 其他人关闭文件，因为调用者可能会持有锁并阻塞其他后端。
	 */
	while ((S_ISLNK(st.st_mode) ? rmdir(path) : unlink(path)) < 0)
	{
		if (errno != EACCES)
			return -1;
		if (++loops > 100)		/* 超时 10 秒 */
			return -1;
		pg_usleep(100000);		/* us */
	}
	return 0;
}

/* 我们在上面未定义这些；现在重新定义以便在下面可能使用 */
#define rename(from, to)		pgrename(from, to)
#define unlink(path)			pgunlink(path)
#endif							/* defined(WIN32) || defined(__CYGWIN__) */


#if defined(WIN32) && !defined(__CYGWIN__)	/* Cygwin 有自己的符号链接 */

/*
 *	pgsymlink 支持：
 *
 *	这个结构是 VC6 winnt.h 中定义的 REPARSE_DATA_BUFFER 的替代品
 *	但在后来的 SDK 函数中被省略。
 *	我们只需要原始结构联合体的 SymbolicLinkReparseBuffer 部分。
 */
typedef struct
{
	DWORD		ReparseTag;
	WORD		ReparseDataLength;
	WORD		Reserved;
	/* SymbolicLinkReparseBuffer */
	WORD		SubstituteNameOffset;
	WORD		SubstituteNameLength;
	WORD		PrintNameOffset;
	WORD		PrintNameLength;
	WCHAR		PathBuffer[FLEXIBLE_ARRAY_MEMBER];
} REPARSE_JUNCTION_DATA_BUFFER;

#define REPARSE_JUNCTION_DATA_BUFFER_HEADER_SIZE   \
		FIELD_OFFSET(REPARSE_JUNCTION_DATA_BUFFER, SubstituteNameOffset)


/*
 *	pgsymlink - 使用 Win32 交接点
 *
 *	参考：http://www.codeproject.com/KB/winsdk/junctionpoints.aspx
 */
int pgsymlink(const char *oldpath, const char *newpath)
{
	HANDLE		dirhandle;
	DWORD		len;
	char		buffer[MAX_PATH * sizeof(WCHAR) + offsetof(REPARSE_JUNCTION_DATA_BUFFER, PathBuffer)];
	char		nativeTarget[MAX_PATH];
	char	   *p = nativeTarget;
	REPARSE_JUNCTION_DATA_BUFFER *reparseBuf = (REPARSE_JUNCTION_DATA_BUFFER *) buffer;

	CreateDirectory(newpath, 0);
	dirhandle = CreateFile(newpath, GENERIC_READ | GENERIC_WRITE,
						   0, 0, OPEN_EXISTING,
						   FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, 0);

	if (dirhandle == INVALID_HANDLE_VALUE)
		return -1;

	/* 确保我们有一个未解析的本地 Win32 路径 */
	if (memcmp("\\??\\", oldpath, 4) != 0)
		snprintf(nativeTarget, sizeof(nativeTarget), "\\??\\%s", oldpath);
	else
		strlcpy(nativeTarget, oldpath, sizeof(nativeTarget));

	while ((p = strchr(p, '/')) != NULL)
		*p++ = '\\';

	len = strlen(nativeTarget) * sizeof(WCHAR);
	reparseBuf->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
	reparseBuf->ReparseDataLength = len + 12;
	reparseBuf->Reserved = 0;
	reparseBuf->SubstituteNameOffset = 0;
	reparseBuf->SubstituteNameLength = len;
	reparseBuf->PrintNameOffset = len + sizeof(WCHAR);
	reparseBuf->PrintNameLength = 0;
	MultiByteToWideChar(CP_ACP, 0, nativeTarget, -1,
						reparseBuf->PathBuffer, MAX_PATH);

	/*
	 * FSCTL_SET_REPARSE_POINT 的编码方式因 SDK 版本而异；
	 * 我们使用自己的定义
	 */
	if (!DeviceIoControl(dirhandle,
						 CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 41, METHOD_BUFFERED, FILE_ANY_ACCESS),
						 reparseBuf,
						 reparseBuf->ReparseDataLength + REPARSE_JUNCTION_DATA_BUFFER_HEADER_SIZE,
						 0, 0, &len, 0))
	{
		LPSTR		msg;

		errno = 0;
		FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
					  FORMAT_MESSAGE_IGNORE_INSERTS |
					  FORMAT_MESSAGE_FROM_SYSTEM,
					  NULL, GetLastError(),
					  MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT),
					  (LPSTR) &msg, 0, NULL);
#ifndef FRONTEND
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not set junction for \"%s\": %s",
						nativeTarget, msg)));
#else
		fprintf(stderr, _("could not set junction for \"%s\": %s\n"),
				nativeTarget, msg);
#endif
		LocalFree(msg);

		CloseHandle(dirhandle);
		RemoveDirectory(newpath);
		return -1;
	}

	CloseHandle(dirhandle);

	return 0;
}

/*
 *	pgreadlink - 使用 Win32 交接点
 */
int pgreadlink(const char *path, char *buf, size_t size)
{
	DWORD		attr;
	HANDLE		h;
	char		buffer[MAX_PATH * sizeof(WCHAR) + offsetof(REPARSE_JUNCTION_DATA_BUFFER, PathBuffer)];
	REPARSE_JUNCTION_DATA_BUFFER *reparseBuf = (REPARSE_JUNCTION_DATA_BUFFER *) buffer;
	DWORD		len;
	int			r;

	attr = GetFileAttributes(path);
	if (attr == INVALID_FILE_ATTRIBUTES)
	{
		_dosmaperr(GetLastError());
		return -1;
	}
	if ((attr & FILE_ATTRIBUTE_REPARSE_POINT) == 0)
	{
		errno = EINVAL;
		return -1;
	}

	h = CreateFile(path,
				   GENERIC_READ,
				   FILE_SHARE_READ | FILE_SHARE_WRITE,
				   NULL,
				   OPEN_EXISTING,
				   FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
				   0);
	if (h == INVALID_HANDLE_VALUE)
	{
		_dosmaperr(GetLastError());
		return -1;
	}

	if (!DeviceIoControl(h,
						 FSCTL_GET_REPARSE_POINT,
						 NULL,
						 0,
						 (LPVOID) reparseBuf,
						 sizeof(buffer),
						 &len,
						 NULL))
	{
		LPSTR		msg;

		errno = 0;
		FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
					  FORMAT_MESSAGE_IGNORE_INSERTS |
					  FORMAT_MESSAGE_FROM_SYSTEM,
					  NULL, GetLastError(),
					  MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT),
					  (LPSTR) &msg, 0, NULL);
#ifndef FRONTEND
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not get junction for \"%s\": %s",
						path, msg)));
#else
		fprintf(stderr, _("could not get junction for \"%s\": %s\n"),
				path, msg);
#endif
		LocalFree(msg);
		CloseHandle(h);
		errno = EINVAL;
		return -1;
	}
	CloseHandle(h);

	/* 明白了，让我们从中获取一些结果 */
	if (reparseBuf->ReparseTag != IO_REPARSE_TAG_MOUNT_POINT)
	{
		errno = EINVAL;
		return -1;
	}

	r = WideCharToMultiByte(CP_ACP, 0,
							reparseBuf->PathBuffer, -1,
							buf,
							size,
							NULL, NULL);

	if (r <= 0)
	{
		errno = EINVAL;
		return -1;
	}

	/*
	 * 如果路径以"\??\"开头，后跟一个“驱动器绝对”路径
	 * （在Windows API中称为RtlPathTypeDriveAbsolute），则去掉该
	 * 前缀。这撤销了pqsymlink()所执行的一些转换，以返回用户
	 * 熟悉的格式。我们不知道如何转换可能在PGDATA之外遇到
	 * 的其他路径类型，因此我们直接返回它们。
	 */
	if (r >= 7 &&
		buf[0] == '\\' &&
		buf[1] == '?' &&
		buf[2] == '?' &&
		buf[3] == '\\' &&
		isalpha(buf[4]) &&
		buf[5] == ':' &&
		buf[6] == '\\')
	{
		memmove(buf, buf + 4, strlen(buf + 4) + 1);
		r -= 4;
	}
	return r;
}

#endif							/* defined(WIN32) && !defined(__CYGWIN__) */
